Zapobieganie zasypywaniu aplikacji wiadomościami WebSocket lub zalewaniu serwera WebSocket wiadomościami przez zastosowanie odwrotnego ciśnienia.
Tło
Interfejs WebSocket API udostępnia interfejs JavaScriptu dla protokołu WebSocket, który umożliwia prowadzenie dwukierunkowej interaktywnej sesji komunikacji między przeglądarką użytkownika a serwerem. Dzięki temu interfejsowi API możesz wysyłać wiadomości na serwer i otrzymywać odpowiedzi wywoływane zdarzeniami bez sprawdzania serwera w celu uzyskania odpowiedzi.
Interfejs Streams API
Interfejs Streams API umożliwia programowi JavaScript dostęp do strumieni danych otrzymanych przez sieć i ich przetwarzanie zgodnie z potrzebami. Ważnym pojęciem w kontekście strumieni jest ciśnienie zwrotne. Jest to proces, w którym pojedynczy strumień lub łańcuch złączy reguluje szybkość odczytu lub zapisu. Gdy strumień lub strumień w dalszej części łańcucha jest nadal zajęty i nie jest jeszcze gotowy do przyjmowania kolejnych fragmentów, wysyła sygnał wstecz przez łańcuch, aby w razie potrzeby spowolnić przesyłanie.
Problem z obecnym interfejsem WebSocket API
Nie można stosować ciśnienia wstecznego do otrzymanych wiadomości.
W przypadku obecnego interfejsu WebSocket API reakcja na wiadomość występuje w WebSocket.onmessage
,
EventHandler
wywoływanym po otrzymaniu wiadomości od serwera.
Załóżmy, że masz aplikację, która musi wykonywać intensywne operacje przetwarzania danych za każdym razem, gdy otrzyma nową wiadomość.
Prawdopodobnie skonfigurujesz przepływ danych w sposób podobny do tego, który przedstawia kod poniżej. Ponieważ await
jest wynikiem wywołania funkcji process()
, wszystko powinno być w porządku, prawda?
// A heavy data crunching operation.
const process = async (data) => {
return new Promise((resolve) => {
window.setTimeout(() => {
console.log('WebSocket message processed:', data);
return resolve('done');
}, 1000);
});
};
webSocket.onmessage = async (event) => {
const data = event.data;
// Await the result of the processing step in the message handler.
await process(data);
};
Nieprawda. Problem z obecnym interfejsem WebSocket API polega na tym, że nie ma możliwości zastosowania odwrotnego ciśnienia.
Gdy wiadomości docierają szybciej, niż metoda process()
może je przetworzyć, proces renderowania albo wypełni pamięć, buforując te wiadomości, albo przestanie odpowiadać z powodu 100% wykorzystania procesora, albo jedno i drugie.
Stosowanie ciśnienia wstecznego w przypadku wysłanych wiadomości jest nieergonomiczne
Zastosowanie sprzężenia zwrotnego w przypadku wysłanych wiadomości jest możliwe, ale wymaga odpytywania właściwości WebSocket.bufferedAmount
, co jest niewydajne i nieergonomiczne.
Ta właściwość tylko do odczytu zwraca liczbę bajtów danych, które zostały umieszczone w kolejce za pomocą wywołań funkcji WebSocket.send()
, ale nie zostały jeszcze przesłane do sieci.
Ta wartość wraca do 0 po wysłaniu wszystkich danych z kolejki, ale jeśli nadal wywołujesz funkcję WebSocket.send()
, będzie ona dalej rosnąć.
Co to jest interfejs WebSocketStream API?
Interfejs WebSocketStream API rozwiązuje problem nieistniejącego lub nieergonomicznego sprzężenia zwrotnego poprzez integrację strumieni z interfejsem WebSocket API. Oznacza to, że odwrotny nacisk może być stosowany „bezpłatnie”, bez dodatkowych kosztów.
Sugerowane zastosowania interfejsu WebSocketStream API
Przykłady witryn, które mogą korzystać z tego interfejsu API:
- aplikacje WebSocket o dużej przepustowości, które muszą zachować interaktywność, w szczególności w przypadku udostępniania ekranu i filmów;
- Podobnie jest w przypadku aplikacji do nagrywania filmów i innych aplikacji, które generują dużo danych w przeglądarce i muszą być przesyłane na serwer. Dzięki temu klient może przestać generować dane zamiast gromadzić je w pamięci.
Obecny stan,
Krok | Stan |
---|---|
1. Tworzenie wyjaśnienia | Zakończono |
2. Tworzenie wstępnej wersji roboczej specyfikacji | W toku |
3. Zbieraj opinie i ulepszaj projekt | W toku |
4. Wersja próbna origin | Zakończono |
5. Uruchom | Nie rozpoczęto |
Jak korzystać z interfejsu WebSocketStream API
Interfejs WebSocketStream API jest oparty na obietnicach, co sprawia, że korzystanie z niego jest naturalne w świecie nowoczesnego JavaScriptu.
Najpierw tworzysz nowy obiekt WebSocketStream
i przekazujesz mu adres URL serwera WebSocket.
Następnie czekasz, aż połączenie będzie opened
, co spowoduje, że ReadableStream
lub WritableStream
.
Po wywołaniu metody ReadableStream.getReader()
otrzymasz obiekt ReadableStreamDefaultReader
, z którego możesz pobierać dane do momentu zakończenia strumienia, czyli do momentu, gdy zwróci on obiekt o formie {value: undefined, done: true}
.read()
W związku z tym wywołanie metody WritableStream.getWriter()
powoduje uzyskanie wartości WritableStreamDefaultWriter
, którą można następnie write()
przekazać.
const wss = new WebSocketStream(WSS_URL);
const {readable, writable} = await wss.opened;
const reader = readable.getReader();
const writer = writable.getWriter();
while (true) {
const {value, done} = await reader.read();
if (done) {
break;
}
const result = await process(value);
await writer.write(result);
}
Obciążenie wsteczne
Co z obiecaną funkcją backpressure?
Otrzymasz je „bezpłatnie”, bez dodatkowych działań.
Jeśli process()
zajmuje więcej czasu, następny komunikat jest wykorzystywany dopiero po przygotowaniu potoku.
Podobnie krok WritableStreamDefaultWriter.write()
jest wykonywany tylko wtedy, gdy jest to bezpieczne.
Zaawansowane przykłady
Drugi argument funkcji WebSocketStream to pakiet opcji umożliwiający rozszerzenie w przyszłości.
Jedyną opcją jest protocols
, która działa tak samo jak drugi argument konstruktora WebSocket:
const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;
Wybrane protocol
oraz potencjalne extensions
są częścią słownika dostępnego w ramach WebSocketStream.opened
.
Wszystkie informacje o bezpośrednim połączeniu są podawane w ramach tej deklaracji, ponieważ w przypadku niepowodzenia połączenia nie są one istotne.
const {readable, writable, protocol, extensions} = await chatWSS.opened;
Informacje o zamkniętym połączeniu WebSocketStream
Informacje, które były dostępne z użyciem zdarzeń WebSocket.onclose
i WebSocket.onerror
w interfejsie WebSocket API, są teraz dostępne za pomocą obietnicy WebSocketStream.closed
.
Obietnica jest odrzucana w przypadku nieprawidłowego zamknięcia. W przeciwnym razie jest przekierowywana do kodu i powodu wysłanego przez serwer.
Wszystkie możliwe kody stanu i ich znaczenie znajdziesz na liście kodów stanu CloseEvent
.
const {code, reason} = await chatWSS.closed;
Zamknięcie połączenia WebSocketStream
Strumień WebSocket można zamknąć za pomocąAbortController
.
Dlatego do konstruktora WebSocketStream
prześlij parametr AbortSignal
.
const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);
Możesz też użyć metody WebSocketStream.close()
, której głównym celem jest umożliwienie określenia kodu i powodu, który jest wysyłany na serwer.
wss.close({code: 4000, reason: 'Game over'});
Progresywne ulepszanie i interoperacyjność
Chrome jest obecnie jedyną przeglądarką, która implementuje interfejs WebSocketStream API.
Ze względu na interoperacyjność z klasycznym interfejsem WebSocket API nie można stosować ciśnienia zwrotnego do otrzymanych wiadomości.
Zastosowanie sprzężenia zwrotnego w przypadku wysłanych wiadomości jest możliwe, ale wymaga odpytywania właściwości WebSocket.bufferedAmount
, co jest niewydajne i nieergonomiczne.
Wykrywanie funkcji
Aby sprawdzić, czy interfejs WebSocketStream API jest obsługiwany, użyj:
if ('WebSocketStream' in window) {
// `WebSocketStream` is supported!
}
Prezentacja
W obsługiwanych przeglądarkach interfejs WebSocketStream API możesz zobaczyć w działaniu w osadzonym iframe lub bezpośrednio na Glitch.
Prześlij opinię
Zespół Chrome chce poznać Twoje wrażenia z korzystania z interfejsu WebSocketStream API.
Prześlij informacje o projektowaniu interfejsu API
Czy coś w interfejsie API nie działa zgodnie z oczekiwaniami? A może brakuje metod lub właściwości, których potrzebujesz do wdrożenia swojego pomysłu? Masz pytania lub uwagi dotyczące modelu zabezpieczeń? Zgłoś problem ze specyfikacją w odpowiednim repozytorium GitHub lub podziel się opinią na temat istniejącego problemu.
Zgłaszanie problemów z implementacją
Czy znalazłeś/znalazłaś błąd w implementacji Chrome?
A może implementacja różni się od specyfikacji?
Zgłoś błąd na stronie new.crbug.com. Pamiętaj, aby podać jak najwięcej szczegółów i proste instrukcje odtwarzania błędu. W polu Składniki wpisz Blink>Network>WebSockets
.
Glitch świetnie sprawdza się w przypadku szybkiego i łatwego odtwarzania przypadków.
Pokaż pomoc dotyczącą interfejsu API
Zamierzasz używać interfejsu WebSocketStream API? Twoje publiczne wsparcie pomaga zespołowi Chrome ustalać priorytety funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest ich wsparcie.
Wyślij tweeta do @ChromiumDev, używając hashtaga #WebSocketStream
i podaj, gdzie i jak go używasz.
Przydatne linki
- Publiczny film wyjaśniający
- Demonstracja interfejsu WebSocketStream API | źródło demonstracji interfejsu WebSocketStream API
- Śledzenie błędu
- Wpis na stronie ChromeStatus.com
- Składnik Blink:
Blink>Network>WebSockets
Podziękowania
Interfejs WebSocketStream API został zaimplementowany przez Adama Rice i Yutaka Hirano.